package top.z.template.parse;

import top.z.template.Source;

/**
 * 在源字符串中, 通过起始索引和结束索引(后者不包括在内)来表示的一个片段
 */
public class Span {
    // 模板源码资源
    private final Source source;
    // 在源码字符串中的开始索引
    private final int    start;
    // 在源码字符串中的结束索引
    private final int    end;
    // 缓存字符串实例, 可减轻 GC 的压力
    private final String text;

    public Span(Source source) {
        this(source, 0, source.getContent().length());
    }

    public Span(Source source, int start, int end) {
        if (start > end) {
            throw new IllegalArgumentException("Start must be <= end.");
        }
        if (start < 0) {
            throw new IndexOutOfBoundsException("Start must be >= 0.");
        }
        if (start > source.getContent().length() - 1) {
            throw new IndexOutOfBoundsException("Start outside of string.");
        }
        if (end > source.getContent().length()) {
            throw new IndexOutOfBoundsException("End outside of string.");
        }
        this.source = source;
        this.start = start;
        this.end = end;
        this.text = source.getContent().substring(start, end);
    }

    public Span(Span start, Span end) {
        if (!start.source.equals(end.source)) {
            throw new IllegalArgumentException("The two spans do not reference the same source.");
        }
        if (start.start > end.end) {
            throw new IllegalArgumentException("Start must be <= end.");
        }
        if (start.start < 0) {
            throw new IndexOutOfBoundsException("Start must be >= 0.");
        }
        if (start.start > start.source.getContent().length() - 1) {
            throw new IndexOutOfBoundsException("Start outside of string.");
        }
        if (end.end > start.source.getContent().length()) {
            throw new IndexOutOfBoundsException("End outside of string.");
        }

        this.source = start.source;
        this.start = start.start;
        this.end = end.end;
        this.text = source.getContent().substring(this.start, this.end);
    }

    // 返回原始文本
    public String getText() {
        return text;
    }

    // 返回开始索引
    public int getStart() {
        return start;
    }

    // 返回结束索引 + 1
    public int getEnd() {
        return end;
    }

    // 返回模板源码
    public Source getSource() {
        return source;
    }

    @Override
    public String toString() {
        return "Span [text=" + text + ", start=" + start + ", end=" + end + "]";
    }

    // 返回Span在源码中的行号
    public Line getLine() {
        int lineStart = start;
        while (true) {
            if (lineStart < 0) {
                break;
            }
            char c = source.getContent().charAt(lineStart);
            if (c == '\n') {
                lineStart = lineStart + 1;
                break;
            }
            lineStart--;
        }
        if (lineStart < 0) {
            lineStart = 0;
        }

        int lineEnd = end;
        while (true) {
            if (lineEnd > source.getContent().length() - 1) {
                break;
            }
            char c = source.getContent().charAt(lineEnd);
            if (c == '\n') {
                break;
            }
            lineEnd++;
        }

        int lineNumber = 0;
        int idx        = lineStart;
        while (idx > 0) {
            char c = source.getContent().charAt(idx);
            if (c == '\n') {
                lineNumber++;
            }
            idx--;
        }
        lineNumber++;

        return new Line(source, lineStart, lineEnd, lineNumber);
    }

    /**
     * 模板源码行号
     */
    public static class Line {
        private final Source source;
        private final int    start;
        private final int    end;
        private final int    line;

        public Line(Source source, int start, int end, int line) {
            this.source = source;
            this.start = start;
            this.end = end;
            this.line = line;
        }

        public Source getSource() {
            return source;
        }

        public int getStart() {
            return start;
        }

        public int getEnd() {
            return end;
        }

        public int getLine() {
            return line;
        }

        public String getText() {
            return source.getContent().substring(start, end);
        }
    }
}
